/** The names of the locator types available. */
builder.locator_types = ['implicit', 'id', 'name', 'dom', 'xpath', 'link', 'css'];

/**
 * A Class representing underlying selenium functions.
 *
 * The constructor takes the name of the public API method.
 *
 * All the properties are generated in advance as they are all needed for any useful interaction.
 */
function SeleniumFunction(api_name) {
  /** The name of the function on the api (e.g click, storeText) */
  this.api_name = api_name;
  
  /** The name of the underlying selenium function (e.g doClick, getText) */
  this.name = this.get_selenium_function_name();

  this.base_name = this.get_base_name();
  
  /**
   * The underlying selenium function itself. If there is no function of that name, put in one
   * that shows an alert.
  */
  this.method = Selenium.prototype[this.name];
  if (!this.method) {
    this.method = function(target, value) { alert('This command cannot be run in the Recorder'); };
  }
  
  /**
   * The names of the parameters that the underlying selenium function expects
   * (e.g ["locator", "value"], ["pattern", false])
   */
  this.parameters = this.get_selenium_parameters();
  
  /** The types of each parameter (e.g ["locator", "string"], ["string", false]) */
  this.parameter_types = this.get_parameter_types();
}

SeleniumFunction.prototype = {
  // Maps API names to the parameters they have.
  tidied_parameters: {
    'keyUp': ['locator', 'key'],
    'keyPress': ['locator', 'key'],
    'keyDown': ['locator', 'key'],
    'type': ['locator', 'text'],
    'typeKeys': ['locator', 'text'],
    'dragAndDrop': ['locator', 'movements'],
    'dragAndDropToObject': ['dragLocator', 'toLocator']
  },

  /**
   * Retrieve the name of the underlying selenium function from the autogenerated API method.
   */
  get_selenium_function_name: function () {
    var api_name = this.api_name.replace("Not", "").replace("AndWait", "");
    var match = /^(get|is|store|assert|verify|waitFor)(.)(.*)/.exec(api_name);
    var guess = null;
    if (match) {
      guess = "get" + match[2].toUpperCase() + match[3];
      if (Selenium.prototype[guess]) {
        return guess;
      }
      guess = "is" + match[2].toUpperCase() + match[3];
      if (Selenium.prototype[guess]) {
        return guess;
      }
    }
    guess = "do" + api_name.substr(0, 1).toUpperCase() + api_name.substr(1);
    return guess;
  },

  /**
   * Retrieve the name of the documented method associated with the actual name.
   */
  get_base_name: function () {
    // If it's "doStuff", return "stuff".
    if (this.name.indexOf("do") == 0) {
      return this.name.substr(2, 1).toLowerCase() + this.name.substr(3);
    } else {
      return this.name;
    }
  },

  /**
   * Given any Javascript first-order-function returns the names of the parameters that it
   * expects.
   *
   * NOTE: This is slightly hacky, but seems to work in opera/ie6/chrome/firefox.
   * It should probably be in some library rather than hidden in here.
   */
  get_parameters: function (function_object) {
    // Use toString to get the function's definition, e.g "function (a, b) { return a + b; }".
    var definition = function_object.toString();
    // Use a regexp to extract the parameter list: e.g spec[1] now contains "a, v".
    var param_list = /\((.*)\)/.exec(definition);
    // If spec[1] is empty or only composed of spaces, there are no params.
    if (!(param_list && param_list[1].replace(/(^ *| *$)/g, ''))) {
      return [];
    }

    // Trim argument names.
    var params = param_list[1].split(",");
    var output = [];
    for (var i = 0; i < params.length; i++) {
      output.push(params[i].replace(/(^ *| *$)/g, ''));
    }

    return output;
  },
  
  /**
   * Get the parameters needed for the selenium API call.
   * NOTE: this shows that some of the autogenerated selenium commands simply cannot work (they need three parameters)
   */
  get_selenium_parameters: function () {
    if (this.tidied_parameters[this.api_name]) {
      return this.tidied_parameters[this.api_name];
    }

    var args = this.get_parameters(this.method);
    if (this.name.substr(0, 3) == "get" && args.length < 2) {
      if (this.api_name.substr(0, 5) == "store") {
        args.push("variable");
      } else if (["assert", "verify", "waitFo"].indexOf(this.api_name.substr(0, 6)) > -1) {
        args.push("equal to");
      }
    }
    return args;
  },

  /**
   * From the names of this.parameters, we can guess whether they are locators, strings or 
   * absent. (Regardless of the fact that the first parameter is called "locator", it may not
   * actually be one.)
   * @return A list of two values, which can be any of [false, 'locator', 'string']. If false,
   *         the function doesn't have that parameter.
   */
  get_parameter_types: function () {
    var params = this.parameters;
    var result = [false, false];

    if (params.length) {
      // Determine type of the first parameter, "locator"
      if ({
          'locator': 1,
          'formLocator': 1,
          'selectLocator': 1,
          'locator1': 1,
          'dragLocator': 1
          }[params[0]])
      {
        result[0] = 'locator';
      } else {
        result[0] = 'string';
      }

      // Determine type of the first parameter, "options"
      if (params.length == 2) {
        if ({ 'locator2': 1, 'toLocator': 1 }[params[1]]) {
          result[1] = 'locator';
        } else {
          result[1] = 'string';
        }
      }
    }
    return result;
  },
  
  /**
   * Given a Windmill params object for an invocation of this function, extract an equivalent
   * Selenium call object.
   * Note that a SeleniumFunction is not an invocation of a Selenium call, it is the call itself.
   * Its invocation with a particular set of parameters is what's returned from this function.
   *
   * selenium call object:
   * { 
   *   method: String
   *   locator: String
   *   altLocator: Object
   *   option: String
   *   altOption: Object
   *   uuid: String
   *   url: String
   * }
   *
   */
  extract_call: function (params) {
    var call = {
      method: this.api_name,
      uuid: params.uuid,
      url: params._url
    };
    
    // Now we need to extract the two parameters of the call: the locator and option. These
    // two blocks of code are largely the same in behaviour.
    if (this.parameter_types[0] == 'string') {
      call.locator = params[this.parameters[0]];
    } else if (this.parameter_types[0] == 'locator') {
      var defaultType = params._default;
      var locators = {
        _default: defaultType
      };

      for (var i = 0; i < builder.locator_types.length; i++) {
        var locator = builder.locator_types[i];
        if (params[locator]) {
          locators[locator] = params[locator];
          if (!defaultType) {
            defaultType = locator;
          }
        }
      }
      call.altLocator = locators ? locators : null;
      call.locator = defaultType ? ((defaultType == 'implicit' ? '' : defaultType + '=') + params[defaultType]) : '';
    }

    if (this.parameter_types[1] == 'string') {
      call.option = params[this.parameters[1]];
    } else if (this.parameter_types[1] == 'locator') {
      var defaultType = params.opt_default;
      var locators = {
        _default: defaultType
      };

      for (var i = 0; i < builder.locator_types.length; i++) {
        var locator = builder.locator_types[i];
        locators[locator] = params['opt' + locator];
        if (locators[locator] && !defaultType) { defaultType = locator; }
      }
      call.altOption = locators ? locators : null;
      call.option = defaultType ? ((defaultType == 'implicit' ? '' : defaultType + '=') + locators[defaultType]) : '';
    }
    return call;
  }
};